using System.Diagnostics;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


	internal abstract class AbstractPersistenceWindow : LockableWindow
	{
		private readonly int recordSize;
		private readonly long position;
		private Buffer buffer = null;
		private readonly int totalSize;
		private readonly int windowSize;

		internal AbstractPersistenceWindow(long position, int recordSize, int totalSize, FileChannel channel, ByteBuffer byteBuffer) : base(channel)
		{
			Debug.Assert(position >= 0, "Illegal position[" + position + "]");
			Debug.Assert(recordSize > 0, "Illegal recordSize[" + recordSize + "]");
			Debug.Assert(channel != null, "Null file channel");
			Debug.Assert(totalSize >= recordSize);

			this.position = position;
			this.recordSize = recordSize;
			this.totalSize = totalSize;
			this.windowSize = totalSize / recordSize;
			this.buffer = new Buffer(this);
			this.buffer.setByteBuffer(byteBuffer);
		}

		public override Buffer getBuffer()
		{
			return buffer;
		}

		public virtual int getRecordSize()
		{
			return recordSize;
		}

		public virtual Buffer getOffsettedBuffer(int id)
		{
			int offset = (int)((id & 0xFFFFFFFFL) - buffer.position()) * recordSize;
			buffer.setOffset(offset);
			return buffer;
		}

		public override long position()
		{
			return position;
		}

		internal virtual void readPosition()
		{
			try
			{
				long fileSize = getFileChannel().size();
				long recordCount = fileSize / recordSize;
			// possible last element not written completely, therefore if
			// fileSize % recordSize can be non 0 and we check > instead of >=
				if (position > recordCount)
				{
				// use new buffer since it will contain only zeros
					return;
				}
				ByteBuffer byteBuffer = buffer.getBuffer();
				byteBuffer.Clear();
				getFileChannel().read(byteBuffer, position * recordSize);
				byteBuffer.Clear();
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to load position[" + position + "] @[" + position * recordSize + "]", e);
			}
		}

		internal virtual void writeOut()
		{
			ByteBuffer byteBuffer = buffer.getBuffer();
			byteBuffer.Clear();
			try
			{
				int count = getFileChannel().write(byteBuffer, position * recordSize);
				Debug.Assert(count == totalSize);
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to write record[" + position + "] @[" + position * recordSize + "]", e);
			}
		}

		public override int size()
		{
			return windowSize;
		}

		public override void force()
		{
			writeOut();
		}

		public override bool Equals(object o)
		{
			if (!(o is AbstractPersistenceWindow))
			{
				return false;
			}
			return position() == ((AbstractPersistenceWindow) o).position();
		}

		public override int GetHashCode()
		{
			return (int) this.position;
		}

		public override string ToString()
		{
			return "PersistenceRow[" + position + "]";
		}

		public override void close()
		{
		// close called after flush all so no need to write out here
			buffer.Close();
		}
	}
}